package com.jy.api.payUtil;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
public class HttpServiceImpl implements IHttpService {
	private InputStream trustStoreInput = null;
	private InputStream keyStoreInput= null;

	public static HttpResult execute(String uri,String requestData,HttpRequestContextType contextType){
		HttpRequestModel httpRequestModel = HttpRequestModel.getInstance(uri, requestData, contextType);
		HttpServiceImpl impl = new HttpServiceImpl();
		return impl.execute(httpRequestModel);
	}
	public static HttpResult execute(String uri,Map<String, Object> requestData,HttpRequestContextType contextType){
		HttpRequestModel httpRequestModel = HttpRequestModel.getInstance(uri, requestData, contextType);
		HttpServiceImpl impl = new HttpServiceImpl();
		return impl.execute(httpRequestModel);
	}
	public static HttpResult execute(String uri,Object javaObject,HttpRequestContextType contextType){
		HttpRequestModel httpRequestModel = HttpRequestModel.getInstance(uri, javaObject, contextType);
		HttpServiceImpl impl = new HttpServiceImpl();
		return impl.execute(httpRequestModel);
	}
	/**
	 * 下载文件，获取文件流
	 * @param requestModel
	 * @return
	 */
	public InputStream executeDownloadWithInpoutStream(HttpRequestModel requestModel){
		CloseableHttpClient httpClient = null;
		InputStream in = null;
		try{
			httpClient = getHttpClient(requestModel);
			HttpRequestBase requestBase = requestModel.getHttpRequestBase();
			RequestConfig requestConfig = RequestConfig.custom()    
			        .setConnectTimeout(requestModel.getConnectTimeout()).setConnectionRequestTimeout(requestModel.getConnectionRequestTimeout())    
			        .setSocketTimeout(requestModel.getSocketTimeout()).build();
			requestBase.setConfig(requestConfig);
			String interfaceName = StringUtils.isNotEmpty(requestModel.getHttpName())?"【"+requestModel.getHttpName()+"】":"";
			log.info(interfaceName+"请求地址-->"+requestBase.getURI().toASCIIString());
			log.info(interfaceName+"请求数据-->"+requestModel.getLogdata());
			HttpResponse response = httpClient.execute(requestBase);
			HttpEntity entity = response.getEntity();
			JlpayAssert.isTrue(response.getStatusLine().getStatusCode()==200, "http通讯异常，异常代码为"+response.getStatusLine().getStatusCode());
			in = entity.getContent();
		}catch (Exception e) {
			log.error("ClientProtocolException", e);
			e.printStackTrace();
		} finally{
			if(keyStoreInput!=null){
				try {
					keyStoreInput.close();
				} catch (IOException e) {
					log.error("keyStoreInput.close Exception", e);
				}
			}
			if(keyStoreInput!=null){
				try {
					keyStoreInput.close();
				} catch (IOException e) {
					log.error("keyStoreInput.close Exception", e);
				}
			}
			if(httpClient!=null){
				try {
					httpClient.close();
				} catch (IOException e) {
					log.error("httpClient.close Exception", e);
				}
			}
		}
		
		return in;
	}
	@Override
	public HttpResult execute(HttpRequestModel requestModel,String p12Path,String password){
		HttpResult responseModel = null;
		CloseableHttpClient httpClient = null;
		try {
			httpClient =getHttpClient(requestModel,p12Path,password);
			HttpRequestBase requestBase = requestModel.getHttpRequestBase();
			RequestConfig requestConfig = RequestConfig.custom()
					.setConnectTimeout(requestModel.getConnectTimeout()).setConnectionRequestTimeout(requestModel.getConnectionRequestTimeout())
					.setSocketTimeout(requestModel.getSocketTimeout()).build();
			requestBase.setConfig(requestConfig);
			String interfaceName = StringUtils.isNotEmpty(requestModel.getHttpName())?"【"+requestModel.getHttpName()+"】":"";
			log.info(interfaceName+"请求地址-->"+requestBase.getURI().toASCIIString());
			log.info(interfaceName+"请求数据-->"+requestModel.getLogdata());
			HttpResponse response = httpClient.execute(requestBase);
			HttpEntity entity = response.getEntity();
			responseModel =  new HttpResult();
			JlpayAssert.isTrue(response.getStatusLine().getStatusCode()==200,"ZZ", "http通讯异常，异常代码为"+response.getStatusLine().getStatusCode());
			responseModel.setStatusCode(response.getStatusLine().getStatusCode());
			String result = EntityUtils.toString(entity, "utf-8");
			log.info(interfaceName+"返回数据-->"+result);
			responseModel.setResult(result);
		} catch (Exception e) {
			log.error("ClientProtocolException", e);
			e.printStackTrace();
		} finally{
			if(keyStoreInput!=null){
				try {
					keyStoreInput.close();
				} catch (IOException e) {
					log.error("keyStoreInput.close Exception", e);
				}
			}
			if(keyStoreInput!=null){
				try {
					keyStoreInput.close();
				} catch (IOException e) {
					log.error("keyStoreInput.close Exception", e);
				}
			}
			if(httpClient!=null){
				try {
					httpClient.close();
				} catch (IOException e) {
					log.error("httpClient.close Exception", e);
				}
			}
		}
		return responseModel;
	}

	@Override
	public HttpResult execute(HttpRequestModel requestModel) {
		HttpResult responseModel = null;
		CloseableHttpClient httpClient = null;
		try {
			httpClient = getHttpClient(requestModel);
			HttpRequestBase requestBase = requestModel.getHttpRequestBase();
			RequestConfig requestConfig = RequestConfig.custom()    
			        .setConnectTimeout(requestModel.getConnectTimeout()).setConnectionRequestTimeout(requestModel.getConnectionRequestTimeout())    
			        .setSocketTimeout(requestModel.getSocketTimeout()).build();
			requestBase.setConfig(requestConfig);
			String interfaceName = StringUtils.isNotEmpty(requestModel.getHttpName())?"【"+requestModel.getHttpName()+"】":"";
			log.info(interfaceName+"请求地址-->"+requestBase.getURI().toASCIIString());
			log.info(interfaceName+"请求数据-->"+requestModel.getLogdata());
			HttpResponse response = httpClient.execute(requestBase);
			HttpEntity entity = response.getEntity();
			responseModel =  new HttpResult();
			JlpayAssert.isTrue(response.getStatusLine().getStatusCode()==200,"ZZ", "http通讯异常，异常代码为"+response.getStatusLine().getStatusCode());
			responseModel.setStatusCode(response.getStatusLine().getStatusCode());
			String result = EntityUtils.toString(entity, "utf-8");
			log.info(interfaceName+"返回数据-->"+result);
			responseModel.setResult(result);
		} catch (Exception e) {
			log.error("ClientProtocolException", e);
			e.printStackTrace();
		} finally{
			if(keyStoreInput!=null){
				try {
					keyStoreInput.close();
				} catch (IOException e) {
					log.error("keyStoreInput.close Exception", e);
				}
			}
			if(keyStoreInput!=null){
				try {
					keyStoreInput.close();
				} catch (IOException e) {
					log.error("keyStoreInput.close Exception", e);
				}
			}
			if(httpClient!=null){
				try {
					httpClient.close();
				} catch (IOException e) {
					log.error("httpClient.close Exception", e);
				}
			}
		}
		return responseModel;
	}

	public  HttpEntity executeEntity(HttpRequestModel requestModel) {
		HttpEntity entity = null;
		CloseableHttpClient httpClient = null;
		try {
			httpClient = getHttpClient(requestModel);
			HttpRequestBase requestBase = requestModel.getHttpRequestBase();
			log.info("请求数据"+requestModel.getLogdata());
			HttpResponse response = httpClient.execute(requestBase);
			 entity = response.getEntity();

		} catch (Exception e) {
			log.error("ClientProtocolException", e);
			e.printStackTrace();
		} finally{
			if(keyStoreInput!=null){
				try {
					keyStoreInput.close();
				} catch (IOException e) {
					log.error("keyStoreInput.close Exception", e);
				}
			}
			if(keyStoreInput!=null){
				try {
					keyStoreInput.close();
				} catch (IOException e) {
					log.error("keyStoreInput.close Exception", e);
				}
			}
		}
		return entity;
	}
	private CloseableHttpClient getHttpClient(HttpRequestModel requestModel,String p12path,String password)throws Exception{
		HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

		KeyStore keyStore =  KeyStore.getInstance("PKCS12");
		keyStoreInput = new FileInputStream(p12path);
		keyStore.load(keyStoreInput, password.toCharArray());
		SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, password.toCharArray()).build();
		// Allow TLSv1 protocol only
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,getProtocolTypes(requestModel), null,
				SSLConnectionSocketFactory.getDefaultHostnameVerifier());
		// allHostsValid);
		// Create a registry of custom connection socket factories for supported
		// protocol schemes.
		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
				.register("https", sslsf).register("http", PlainConnectionSocketFactory.INSTANCE).build();
		PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
		// Create socket configuration
		SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(500).setTcpNoDelay(true).build();// 小数据网络包
		// Configure the connection manager to use socket configuration either
		// by default or for a specific host.
		connManager.setDefaultSocketConfig(socketConfig);
		// Validate connections after 1 sec of inactivity
		connManager.setValidateAfterInactivity(1000);
		// Configure total max or per route limits for persistent connections
		// that can be kept in the pool or leased by the connection manager.
		RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.DEFAULT)
				.setExpectContinueEnabled(true).build();
		httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig);
		httpClientBuilder.setDefaultCookieStore(new BasicCookieStore());
//		httpClientBuilder.
		connManager.setMaxTotal(200);
//		connManager.
		connManager.setDefaultMaxPerRoute(50);
		httpClientBuilder.setConnectionManager(connManager);
		httpClientBuilder.evictExpiredConnections().evictIdleConnections(50000L, TimeUnit.MILLISECONDS).setSSLSocketFactory(sslsf);
		return httpClientBuilder.build();
	}
	private CloseableHttpClient getHttpClient(HttpRequestModel requestModel)throws Exception{
		HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
		if(requestModel.getProxy()!=null){
			httpClientBuilder.setProxy(requestModel.getProxy());
		}
		if(requestModel.getCredsProvider()!=null){
			httpClientBuilder.setDefaultCredentialsProvider(requestModel.getCredsProvider());
		}
		if(requestModel.isHttps()){
			if(requestModel.isTwoAuth()){
				buildTwoWaySSL(requestModel, httpClientBuilder);
			}else{
				buildSignWaySSL(requestModel, httpClientBuilder);
			}
		}else{
			Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
					.register("http", new PlainConnectionSocketFactory())
					.build();
			PoolingHttpClientConnectionManager cm =new PoolingHttpClientConnectionManager(socketFactoryRegistry);
			cm.setMaxTotal(200);
			cm.setDefaultMaxPerRoute(20);
			httpClientBuilder.setConnectionManager(cm);
		}
		return httpClientBuilder.build();
	}


	private void buildTwoWaySSL(HttpRequestModel requestModel,HttpClientBuilder httpClientBuilder)throws Exception{
		log.info("构建https双向认证");
		TwoWayAuthModel twoWayAuthModel = requestModel.getTwoWayAuthModel();
		KeyStore keyStore =  KeyStore.getInstance(twoWayAuthModel.getClientCertType());
		keyStoreInput = new FileInputStream(twoWayAuthModel.getClientCertFilePath());
    	keyStore.load(keyStoreInput, twoWayAuthModel.getClientCertPassword().toCharArray());

    	KeyStore trustStore = KeyStore.getInstance(twoWayAuthModel.getServerCertType());
    	trustStoreInput = new FileInputStream(new File(twoWayAuthModel.getServerCertFilePath()));
    	char[] serverPassword = StringUtils.isNotEmpty(twoWayAuthModel.getServerCertPassword())?twoWayAuthModel.getServerCertPassword().toCharArray():null;
        trustStore.load(trustStoreInput, serverPassword);
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, twoWayAuthModel.getClientCertPassword().toCharArray())
				.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
		// Allow TLSv1 protocol only
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,getProtocolTypes(requestModel), null,
				SSLConnectionSocketFactory.getDefaultHostnameVerifier());
		// allHostsValid);
		// Create a registry of custom connection socket factories for supported
		// protocol schemes.
		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
				.register("https", sslsf).register("http", PlainConnectionSocketFactory.INSTANCE).build();
		PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
		// Create socket configuration
		SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(500).setTcpNoDelay(true).build();// 小数据网络包
		// Configure the connection manager to use socket configuration either
		// by default or for a specific host.
		connManager.setDefaultSocketConfig(socketConfig);
		// Validate connections after 1 sec of inactivity
		connManager.setValidateAfterInactivity(1000);
		// Configure total max or per route limits for persistent connections
		// that can be kept in the pool or leased by the connection manager.
		RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.DEFAULT)
				.setExpectContinueEnabled(true).build();
		httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig);
		httpClientBuilder.setDefaultCookieStore(new BasicCookieStore());
//		httpClientBuilder.
		connManager.setMaxTotal(200);
//		connManager.
		connManager.setDefaultMaxPerRoute(50);
		httpClientBuilder.setConnectionManager(connManager);
		httpClientBuilder.evictExpiredConnections().evictIdleConnections(50000L, TimeUnit.MILLISECONDS).setSSLSocketFactory(sslsf);
	}

	private void buildSignWaySSL(HttpRequestModel requestModel,HttpClientBuilder httpClientBuilder)throws Exception{
		log.info("构建https单向认证");
			SSLContextBuilder builder = new SSLContextBuilder();
            // 全部信任 不做身份鉴定
            builder.loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                    return true;
                }
            });
            SSLConnectionSocketFactory  sslsf = new SSLConnectionSocketFactory(builder.build(), getProtocolTypes(requestModel), null, NoopHostnameVerifier.INSTANCE);

            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                    .register("https", sslsf)
                    .register("http", new PlainConnectionSocketFactory())
                    .build();
            PoolingHttpClientConnectionManager cm =new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            cm.setMaxTotal(200);
            cm.setDefaultMaxPerRoute(20);
            httpClientBuilder.setConnectionManager(cm);
            httpClientBuilder.setSSLSocketFactory(sslsf);

	}

	private String[] getProtocolTypes(HttpRequestModel requestModel){
		ProtocolType[] protocolTypes = requestModel.getProtocols();
		if(protocolTypes==null){
			protocolTypes = ProtocolType.values();
		}
		String[] protocol = new String[protocolTypes.length];
		for(int i=0;i<protocol.length;i++){
			protocol[i]=protocolTypes[i].getDesc();
		}
		return protocol;
	}
}
